package master;

import java.awt.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;

import master.event.*;
import master.quantumComputerIO.gate.*;
import gates.*;

public class QuantumNetwork implements GateGenerationListener, NetworkEventInitiator, Serializable{
	QuantumRegister inputRegister;//the input register
	QuantumRegister outputRegister;//the output
	QuantumGate [] gates;
	Object [][] inputArguments;
	private int currentStep = 0;//the current gate waiting to be applied
	
	public QuantumNetwork(QuantumRegister inputRegister){
		this.inputRegister = inputRegister;
		this.outputRegister = inputRegister.copy();
		gates = new QuantumGate[0];
		inputArguments = new Object[0][];
	}
	public QuantumGate [] getGates(){
		return gates;
	}
	public int getCurrentStep(){
		return currentStep;
	}
	public QuantumRegister getInputRegister(){
		return inputRegister;
	}
	public QuantumRegister getOutputRegister(){
		return outputRegister;
	}
	public void setInputRegister(QuantumRegister inputRegister){
		this.inputRegister = inputRegister;
	}
	public void draw(Graphics g, int x , int y, float scale){
		int drawX = x;
		int drawY = y;
		g = (Graphics2D)g;
		g.drawRoundRect(drawX, drawY, (int)scale, (int)(scale*inputRegister.getQBitLength()),2,2);
		
		drawX += (int)scale;
		
		
		for(int i = 0 ; i <= gates.length ;i++){
			
			if(i == currentStep){
				int Ypos = drawY +(int)(scale*inputRegister.getQBitLength());
				g.fillPolygon(
						new int[]{drawX,drawX,drawX+(int)(scale)},
						new int[]{Ypos,Ypos+(int)(scale),Ypos+(int)(scale/2)}, 3);
			}
			//g.fillOval(drawX, y+(int)(scale*inputRegister.getQBitLength()), (int)scale, (int)(scale));
			
			if(i<gates.length){
				inputRegister.draw(g,drawX, drawY+(int)scale/2, scale);
				drawX+= (int)scale;
				inputRegister.draw(g,drawX, drawY+(int)scale/2, scale);
				
				gates[i].draw(g, drawX, drawY, scale);
				
				inputRegister.draw(g,drawX+(int)scale, drawY+(int)scale/2, scale);
				drawX+= (int)scale*2;
			}
		}
		
		inputRegister.draw(g,drawX, drawY+(int)scale/2, scale);
		drawX+=(int)scale;
		g.drawRoundRect(drawX, drawY, (int)scale, (int)(scale*inputRegister.getQBitLength()),2,2);
		
		drawY = (int) (y + scale * (inputRegister.getQBitLength()+1));
		drawX = x;
		for(int i = 0 ; i < gates.length ;i++){
			drawY += (int)scale;
			String str = i+": "+gates[i].toString() + "{";
			for(Object j : inputArguments[i])
				str += j.toString() + ", ";
			str+="}";
			g.drawString(str, drawX, drawY);
		}
	}
	
	public void addGate(QuantumGate gate, Object [] gateInputArguments) throws GateException{
		QuantumGate [] newGateArr = new QuantumGate[gates.length + 1];
		Object [][] newInputArr = new Object[inputArguments.length+1][];
		
		for(int i = 0 ; i < gates.length ; i++){
			newGateArr[i] = gates[i];
			newInputArr[i] = inputArguments[i];
		}
		
		newGateArr[gates.length] = gate;
		newInputArr[inputArguments.length] = gateInputArguments;
		gates = newGateArr;
		inputArguments = newInputArr;
		createNetworkEvent();
	}
	public void removeGate(int i){
		if(i > -1 && i < gates.length){
			QuantumGate [] newArr = new QuantumGate[gates.length - 1];
			Object [][] newInpArr = new Object[gates.length-1][];
			for(int j = 0 ; j < i ; j++){
				newArr[j] = gates[j];
				newInpArr[j] = inputArguments[j];
			}
			for(int j = i+1 ; j < gates.length  ;j++){
				newArr[j-1] = gates[j];
				newInpArr[j-1] = inputArguments[j];
			}
			gates = newArr;
			inputArguments = newInpArr;
		}
		reset();
		createNetworkEvent();
	}
	public QuantumRegister getStep(int i){
		if(i >= gates.length || i < -1)
			throw new IllegalArgumentException("No such gate");
		
		if(i < currentStep)
			reset();
		
		QuantumRegister temp = outputRegister.copy();
		
		//TODO this is flawed, need a way to generalise argument length
		for(int j = currentStep; j <= i ; j++)
			gates[j].actOnReg(temp);
		
		
		for(int p = 0 ; p < outputRegister.getDimension();p++){
			outputRegister.setCoefficient(
					temp.getCoefficient(p)
					, p);
		}
		
		currentStep=i+1;
		createNetworkEvent();
		return outputRegister;
	}
	
	public void reset(){
		currentStep = 0 ;
		outputRegister.copyValues(inputRegister);
		createNetworkEvent();
	}
	public int getSteps(){
		return gates.length;
	}
	public void gateGenerated(GateGenerationEvent e) {
		try{
			this.addGate(e.getGate(),e.getConstructorArguments());
		}catch(GateException eg){
			JOptionPane.showMessageDialog(null, "gate not compatible with network");
		}
	}
	public String toString(){
		String str = inputRegister.toScript()+"\n";
		for(int i = 0 ; i < gates.length ;i++){
			str += gates[i].getClass().getName() + " Args :";
			for(Object j : inputArguments[i])
				str += j.toString() + "\t";
			str += "\n";
		}
		return str;
	}
	
	
	List<NetworkEventListener> listeners = new ArrayList<NetworkEventListener>();
	
	public void addNetworkListener(NetworkEventListener l) {
		listeners.add(l);
	}
	public void createNetworkEvent() {
		NetworkEvent e = new NetworkEvent(this,0,this);
		for(NetworkEventListener l : listeners)
			l.networkEvent(e);
	}
}
